/*************************************************************
 * File: HibernatePersistenceSupport.java
 * Copyright (c) 2012
 * Author: Huang Shaobin
 * Email: Shaobin.Software@gmail.com Shaobin_Software@163.com 
 * Date: Feb 3, 2013
 * Description: 
 ************************************************************/

package snow.core.persistence.hibernate;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Junction;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.util.Assert;

import snow.core.persistence.Pagination;
import snow.core.persistence.PersistenceSupport;
import snow.core.persistence.query.AndedConditions;
import snow.core.persistence.query.BinaryOperator;
import snow.core.persistence.query.Condition;
import snow.core.persistence.query.Conditions;
import snow.core.persistence.query.Operator;
import snow.core.persistence.query.Order;
import snow.core.persistence.query.OredConditions;

/**
 * 使用Hibernate框架实现的序列化工具
 * 
 * @author Shaobin.Software@gmail.com Shaobin_Software@163.com
 * @version 1.0
 */
public class HibernatePersistenceSupport extends PersistenceSupport implements InitializingBean {

	protected final Log log = LogFactory.getLog(getClass());

	private boolean cacheQueries = false;
	private boolean alwaysUseNewSession = false;
	private SessionFactory sessionFactory;
	private String queryCacheRegion;
	private int fetchSize = 0;
	private int maxResults = 0;

	protected void addCriteriaFrom(Junction junction, List<Condition> conditionList) {
		if (CollectionUtils.isNotEmpty(conditionList)) {
			for (final Condition condition : conditionList) {
				if (junction != null && condition != null && condition.isValid()) {
					Operator operator = condition.getOperator();
					final String name = condition.getName();
					if (condition.isNoValue()) {
						if (Operator.IS_NULL == operator) {
							junction.add(Restrictions.isNull(name));
						} else if (Operator.NOT_NULL == operator) {
							junction.add(Restrictions.isNotNull(name));
						}
					} else if (condition.isBetweenValue()) {
						junction.add(Restrictions.between(name, condition.getValue(), condition.getSecondValue()));
					} else if (condition.isListValue()) {
						Object value = condition.getValue();
						if (value instanceof Collection) {
							junction.add(Restrictions.in(name, (Collection<?>) value));
						}
					} else if (condition.isSingleValue()) {
						if (operator instanceof BinaryOperator) {
							Object value = condition.getValue();
							BinaryOperator binaryOperator = (BinaryOperator) operator;
							if (BinaryOperator.EQ == binaryOperator) {
								junction.add(Restrictions.eq(name, value));
							} else if (BinaryOperator.GE == binaryOperator) {
								junction.add(Restrictions.ge(name, value));
							} else if (BinaryOperator.GT == binaryOperator) {
								junction.add(Restrictions.gt(name, value));
							} else if (BinaryOperator.LE == binaryOperator) {
								junction.add(Restrictions.le(name, value));
							} else if (BinaryOperator.LIKE == binaryOperator) {
								junction.add(Restrictions.like(name, value));
							} else if (BinaryOperator.LT == binaryOperator) {
								junction.add(Restrictions.lt(name, value));
							} else if (BinaryOperator.NE == binaryOperator) {
								junction.add(Restrictions.ne(name, value));
							}
						}
					}
				}
			}
		}
	}

	protected void addCriteriaFrom(List<Conditions> conditionsList, Junction junction) {
		if (CollectionUtils.isNotEmpty(conditionsList)) {
			for (final Conditions conditions : conditionsList) {
				if (conditions instanceof AndedConditions) {
					junction.add(toJunctionFrom((AndedConditions) conditions));
				} else if (conditions instanceof OredConditions) {
					junction.add(toJunctionFrom((OredConditions) conditions));
				}
			}
		}
	}

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(sessionFactory, "The property [sessionFactory] must not be null!");
	}

	/**
	 * @param session
	 * @return
	 */
	protected Session createSessionProxy(Session session) {
		return session;
	}

	public void delete(Object entity) {
		delete(entity, null);
	}

	public void delete(final Object entity, final LockMode lockMode) {
		executeWithNativeSession(new HibernateCallback<Object>() {

			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				lock(session, entity, lockMode);
				session.delete(entity);
				return null;
			}

		});
	}

	protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession) {
		final Session session = enforceNewSession ? getSessionFactory().openSession() : getSession();
		final Session sessionToExpose = enforceNativeSession ? session : createSessionProxy(session);
		try {
			T result = action.doInHibernate(sessionToExpose);
			return result;
		} catch (HibernateException e) {
			log.error(e.getMessage(), e);
		} catch (SQLException e) {
			log.error(e.getMessage(), e);
		}
		return null;
	}

	public <T> T executeWithNativeSession(HibernateCallback<T> action) {
		return doExecute(action, false, true);
	}

	public <T> List<T> find(final Class<T> entityClass, final Conditions conditions, final List<Order> orders, final int start, final int limit) {
		return findByCriteria(toDetachedCriteriaFrom(conditions, orders, entityClass), entityClass, start, limit);
	}

	public <T> T find(Class<T> entityClass, Serializable id) {
		return get(entityClass, id);
	}

	public <T> List<T> findByCriteria(final DetachedCriteria detachedCriteria, final Class<T> entityClass, final int start, final int limit) {
		return executeWithNativeSession(new HibernateCallback<List<T>>() {

			public List<T> doInHibernate(Session session) throws HibernateException, SQLException {
				Criteria criteria = detachedCriteria.getExecutableCriteria(session);
				HibernatePersistenceSupport.this.prepareCriteria(criteria);
				if (start > 0) {
					criteria.setFirstResult(start);
				}
				if (limit > 0) {
					criteria.setMaxResults(limit);
				}
				final List<?> list = criteria.list();
				List<T> records = Collections.emptyList();
				if (CollectionUtils.isNotEmpty(list)) {
					for (final Object item : list) {
						if (entityClass.isInstance(item)) {
							records.add(entityClass.cast(item));
						}
					}
				}
				return records;
			}

		});
	}

	public Pagination findByCriteriaForPaging(final DetachedCriteria detachedCriteria, final int start, final int limit) {
		return executeWithNativeSession(new HibernateCallback<Pagination>() {

			public Pagination doInHibernate(Session session) throws HibernateException, SQLException {
				final Criteria criteria = detachedCriteria.getExecutableCriteria(session);
				HibernatePersistenceSupport.this.prepareCriteria(criteria);
				final Number totalCounts = (Number) criteria.setProjection(Projections.rowCount()).uniqueResult(); // 查询总条数
				if (start >= 0) {
					criteria.setFirstResult(start);
				}
				if (limit > 0) {
					criteria.setMaxResults(limit);
				}
				criteria.setProjection(null); // 清空
				return new Pagination(criteria.list(), Long.valueOf(totalCounts.longValue()));
			}

		});
	}

	public <T> T findByCriteriaForUnique(DetachedCriteria detachedCriteria, Class<T> entityClass) {
		final List<T> results = findByCriteria(detachedCriteria, entityClass, 0, 1);
		return CollectionUtils.isEmpty(results) ? null : results.get(0);
	}

	public Pagination findForPaging(final Class<?> entityClass, final Conditions conditions, final List<Order> orders, final int start, final int limit) {
		return findByCriteriaForPaging(toDetachedCriteriaFrom(conditions, orders, entityClass), start, limit);
	}

	public <T> T findForUnique(Conditions conditions, List<Order> orders, Class<T> entityClass) {
		return findByCriteriaForUnique(toDetachedCriteriaFrom(conditions, orders, entityClass), entityClass);
	}

	public <T> T get(Class<T> entityClass, Serializable id) {
		return get(entityClass, id, null);
	}

	public <T> T get(final Class<T> entityClass, final Serializable id, final LockMode lockMode) {
		return executeWithNativeSession(new HibernateCallback<T>() {

			public T doInHibernate(Session session) throws HibernateException, SQLException {
				final Object entity = lockMode != null ? session.get(entityClass, id, new LockOptions(lockMode)) : session.get(entityClass, id);
				if (entityClass != null && entityClass.isInstance(entity)) {
					return entityClass.cast(entity);
				}
				return null;
			}

		});
	}

	public int getFetchSize() {
		return this.fetchSize;
	}

	public int getMaxResults() {
		return this.maxResults;
	}

	public String getQueryCacheRegion() {
		return this.queryCacheRegion;
	}

	public Session getSession() {
		if (isAlwaysUseNewSession()) {
			return getSessionFactory().openSession();
		}
		return getSessionFactory().getCurrentSession();
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public boolean isAlwaysUseNewSession() {
		return alwaysUseNewSession;
	}

	public boolean isCacheQueries() {
		return this.cacheQueries;
	}

	public <T> T load(Class<T> entityClass, Serializable id) {
		return load(entityClass, id, null);
	}

	public <T> T load(final Class<T> entityClass, final Serializable id, final LockMode lockMode) {
		return executeWithNativeSession(new HibernateCallback<T>() {

			public T doInHibernate(Session session) throws HibernateException, SQLException {
				final Object entity = lockMode != null ? session.load(entityClass, id, new LockOptions(lockMode)) : session.load(entityClass, id);
				if (entityClass != null && entityClass.isInstance(entity)) {
					return entityClass.cast(entity);
				}
				return null;
			}

		});
	}

	protected void lock(Session session, Object entity, LockMode lockMode) {
		if (session != null && entity != null && lockMode != null) {
			session.buildLockRequest(new LockOptions(lockMode)).lock(entity);
		}
	}

	/**
	 * 对Hibernate查询做一些预处理操作，包括设置缓存
	 * 
	 * @param criteria
	 */
	protected void prepareCriteria(Criteria criteria) {
		if (isCacheQueries()) {
			criteria.setCacheable(true);
			if (getQueryCacheRegion() != null) {
				criteria.setCacheRegion(getQueryCacheRegion());
			}
		}
		if (getFetchSize() > 0) {
			criteria.setFetchSize(getFetchSize());
		}
		if (getMaxResults() > 0) {
			criteria.setMaxResults(getMaxResults());
		}
	}

	public <ID extends Serializable> ID save(Object entity, Class<ID> idClass) {
		return save(entity, idClass, null);
	}

	public <ID extends Serializable> ID save(final Object entity, final Class<ID> idClass, final LockMode lockMode) {
		return executeWithNativeSession(new HibernateCallback<ID>() {

			public ID doInHibernate(Session session) throws HibernateException, SQLException {
				lock(session, entity, lockMode);
				Serializable id = session.save(entity);
				if (idClass != null && idClass.isInstance(id)) {
					return idClass.cast(id);
				}
				return null;
			}
		});
	}

	public void setAlwaysUseNewSession(boolean alwaysUseNewSession) {
		this.alwaysUseNewSession = alwaysUseNewSession;
	}

	public void setCacheQueries(boolean cacheQueries) {
		this.cacheQueries = cacheQueries;
	}

	public void setFetchSize(int fetchSize) {
		this.fetchSize = fetchSize;
	}

	public void setMaxResults(int maxResults) {
		this.maxResults = maxResults;
	}

	public void setQueryCacheRegion(String queryCacheRegion) {
		this.queryCacheRegion = queryCacheRegion;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	protected DetachedCriteria toDetachedCriteriaFrom(final Conditions conditions, List<Order> orders, Class<?> entityClass) {
		final DetachedCriteria detachedCriteria = DetachedCriteria.forClass(entityClass);
		if (conditions instanceof AndedConditions) {
			detachedCriteria.add(toJunctionFrom((AndedConditions) conditions));
		} else if (conditions instanceof OredConditions) {
			detachedCriteria.add(toJunctionFrom((OredConditions) conditions));
		}
		if (CollectionUtils.isNotEmpty(orders)) {
			for (final Order order : orders) {
				final String propertyName = order == null ? null : order.getPropertyName();
				if (StringUtils.isNotBlank(propertyName)) {
					if (order.isAscending()) {
						detachedCriteria.addOrder(org.hibernate.criterion.Order.asc(propertyName));
					} else {
						detachedCriteria.addOrder(org.hibernate.criterion.Order.desc(propertyName));
					}
				}
			}
		}
		return detachedCriteria;
	}

	protected Junction toJunctionFrom(AndedConditions andedConditions) {
		Conjunction conjunction = Restrictions.conjunction();
		if (andedConditions != null) {
			addCriteriaFrom(conjunction, andedConditions.getConditions());
			addCriteriaFrom(andedConditions.getAndedConditions(), conjunction);
		}
		return conjunction;
	}

	protected Junction toJunctionFrom(OredConditions oredConditions) {
		Disjunction disjunction = Restrictions.disjunction();
		if (oredConditions != null) {
			addCriteriaFrom(disjunction, oredConditions.getConditions());
			addCriteriaFrom(oredConditions.getOredConditions(), disjunction);
		}
		return disjunction;
	}

	public void update(Object entity) {
		update(entity, null);
	}

	/**
	 * @param entity
	 * @param object
	 */
	public void update(final Object entity, final LockMode lockMode) {
		executeWithNativeSession(new HibernateCallback<Object>() {

			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				lock(session, entity, lockMode);
				session.update(entity);
				return null;
			}

		});
	}

}
